﻿@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
    @if (enableDataAnnotationsSupport)
    {
        <DataAnnotationsValidator />
    }

    <p class="name">
        Name: <InputText @bind-Value="person.Name" placeholder="Enter your name" />
    </p>
    <p class="email">
        Email: <InputText @bind-Value="person.Email" />
        <ValidationMessage For="@(() => person.Email)" class="special-email-css-class-override" />
    </p>
    <p class="confirm-email">
        Email: <InputText @bind-Value="person.ConfirmEmail" />
        <ValidationMessage For="@(() => person.ConfirmEmail)" />
    </p>
    <p class="age">
        Age (years): <InputNumber @bind-Value="person.AgeInYears" placeholder="Enter your age" />
    </p>
    <p class="height">
        Height (optional): <InputNumber @bind-Value="person.OptionalHeight" />
    </p>
    <p class="description">
        Description: <InputTextArea @bind-Value="person.Description" placeholder="Tell us about yourself" />
    </p>
    <p class="renewal-date">
        Renewal date: <InputDate @bind-Value="person.RenewalDate" placeholder="Enter the date" />
    </p>
    <p class="expiry-date">
        Expiry date (optional): <InputDate @bind-Value="person.OptionalExpiryDate" />
    </p>
    <p class="departure-time">
        Departure time:<br>
        <input id="time-seconds-checkbox" type="checkbox" @bind-value="includeTimeSeconds" />Include seconds<br>
        <InputDate
            id="time-input"
            Type="InputDateType.Time"
            @bind-Value="person.DepartureTime"
            placeholder="Enter the time"
            step="@(includeTimeSeconds ? 1 : 60)" />
    </p>
    <p class="visit-month">
        Month of visit: <InputDate Type="InputDateType.Month" @bind-Value="person.VisitMonth" placeholder="Enter the month of your visit" />
    </p>
    <p class="appointment-date-time">
        Appointment date and time:<br>
        <input id="datetime-local-seconds-checkbox" type="checkbox" @bind-value="includeDateTimeLocalSeconds" />Include seconds<br>
        <InputDate
            id="datetime-local-input"
            Type="InputDateType.DateTimeLocal"
            @bind-Value="person.AppointmentDateAndTime"
            placeholder="Enter the appointment date and time"
            step="@(includeDateTimeLocalSeconds ? 1 : 60)" />
    </p>
    <p class="ticket-class">
        Ticket class:
        @*We specify 'multiple' here, but it has no effect since we are not binding to an array type.*@
        <InputSelect @bind-Value="person.TicketClass" size="4" multiple>
            <option>(select)</option>
            <option value="@TicketClass.Economy">Economy class</option>
            <option value="@TicketClass.Premium">Premium class</option>
            <option value="@TicketClass.First">First class</option>
        </InputSelect>
        <span id="selected-ticket-class">@person.TicketClass</span>
    </p>
    <p class="cities">
        @*Here, the 'multiple' attribute is inferred because we are binding to an array type.*@
        <InputSelect @bind-Value="person.SelectedCities">
            <option value="@City.SanFrancisco">San Francisco</option>
            <option value="@City.Tokyo">Tokyo</option>
            <option value="@City.London">London</option>
            <option value="@City.Madrid">Madrid</option>
        </InputSelect>
    </p>
    <p class="select-multiple-hostile">
        <InputSelect @bind-Value="person.HostileStrings">
            <option value="@("\"")">@("\"")</option>
            <option value="{">{</option>
            <option value="@("")">Empty string</option>
        </InputSelect>
        <span>@string.Join(", ", person.HostileStrings)</span>
    </p>
    <p class="select-bool-values">
        T/F: 77 + 33 = 100<br>
        <InputSelect @bind-Value="person.IsSelectMathStatementTrue">
            <option>(select)</option>
            <option value="true">true</option>
            <option value="false">false</option>
        </InputSelect>
    </p>
    <p class="airline">
        <InputRadioGroup @bind-Value="person.Airline">
            Airline:
            <br>
            @foreach (var airline in (Airline[])Enum.GetValues(typeof(Airline)))
            {
                <InputRadio Value="airline" extra="additional" />
                @airline.ToString();
                <br>
            }
        </InputRadioGroup>
    </p>
    <p class="nested-radio-group">
        Pick one color and one country:
        <InputRadioGroup Name="country" @bind-Value="person.Country">
            <InputRadioGroup Name="color" @bind-Value="person.FavoriteColor">
                <InputRadio Name="color" Value="Color.Red" />red<br>
                <InputRadio Name="country" Value="Country.Japan" />japan<br>
                <InputRadio Name="color" Value="Color.Green" />green<br>
                <InputRadio Name="country" Value="Country.Yemen" />yemen<br>
                <InputRadio Name="color" Value="Color.Blue" />blue<br>
                <InputRadio Name="country" Value="Country.Latvia" />latvia<br>
                <InputRadio Name="color" Value="Color.Orange" />orange<br>
            </InputRadioGroup>
        </InputRadioGroup>
    </p>
    <p class="radio-group-bool-values">
        T/F: 7 * 3 = 21<br>
        <InputRadioGroup @bind-Value="person.IsRadioMathStatementTrue">
            <InputRadio Value="true" />true<br>
            <InputRadio Value="false" />false<br>
        </InputRadioGroup>
    </p>
    <p class="socks">
        Socks color: <InputText @bind-Value="person.SocksColor" />
    </p>
    <p class="accepts-terms">
        Accepts terms: <InputCheckbox @bind-Value="person.AcceptsTerms" title="You have to check this" />
    </p>
    <p class="is-evil">
        Is evil: <InputCheckbox @bind-Value="person.IsEvil" />
    </p>
    <p class="username">
        Username (optional): <InputText @bind-Value="person.Username" />
        <button type="button" @onclick="@TriggerAsyncValidationError">Trigger async error</button>
    </p>

    <button type="submit">Submit</button>

    <p class="all-errors">
        <ValidationSummary />
    </p>
</EditForm>

<ul>@foreach (var entry in submissionLog) { <li class="submission-log-entry">@entry</li> }</ul>

<button id="toggle-dataannotations" @onclick="ToggleDataAnnotations">Toggle DataAnnotations support</button>

@code {
    protected virtual bool UseExperimentalValidator => false;

    Person person = new Person();
    EditContext editContext;
    ValidationMessageStore customValidationMessageStore;
    bool enableDataAnnotationsSupport = true;
    bool includeTimeSeconds;
    bool includeDateTimeLocalSeconds;

    protected override void OnInitialized()
    {
        editContext = new EditContext(person);
        editContext.SetFieldCssClassProvider(new CustomFieldCssClassProvider());
        customValidationMessageStore = new ValidationMessageStore(editContext);
    }

    // Usually this would be in a different file
    class Person
    {
        [Required(ErrorMessage = "Enter a name"), StringLength(10, ErrorMessage = "That name is too long")]
        public string Name { get; set; }

        [EmailAddress(ErrorMessage = "That doesn't look like a real email address")]
        [StringLength(10, ErrorMessage = "We only accept very short email addresses (max 10 chars)")]
        public string Email { get; set; }

        [Compare(nameof(Email), ErrorMessage = "Email and confirm email do not match.")]
        public string ConfirmEmail { get; set; }

        [Range(0, 200, ErrorMessage = "Nobody is that old")]
        public int AgeInYears { get; set; }

        public float? OptionalHeight { get; set; }

        public DateTime RenewalDate { get; set; } = DateTime.Now;

        public DateTimeOffset? OptionalExpiryDate { get; set; }

        public TimeOnly DepartureTime { get; set; } = TimeOnly.MinValue;

        public DateOnly VisitMonth { get; set; } = new DateOnly(DateTime.Now.Year, 1, 1);

        public DateTime AppointmentDateAndTime { get; set; } = DateTime.Today;

        [Required, Range(typeof(bool), "true", "true", ErrorMessage = "Must accept terms")]
        public bool AcceptsTerms { get; set; }

        [Required, Range(typeof(bool), "false", "false", ErrorMessage = "Must not be evil")]
        public bool IsEvil { get; set; } = true;

        [Required, StringLength(20, ErrorMessage = "Description is max 20 chars")]
        public string Description { get; set; }

        [Required, EnumDataType(typeof(TicketClass))]
        public TicketClass TicketClass { get; set; }

        [Required]
        [Range(typeof(Airline), nameof(Airline.BestAirline), nameof(Airline.NoNameAirline), ErrorMessage = "Pick a valid airline.")]
        public Airline Airline { get; set; } = Airline.Unknown;

        [Required, EnumDataType(typeof(Color))]
        public Color? FavoriteColor { get; set; } = null;

        [Required, EnumDataType(typeof(Country))]
        public Country? Country { get; set; } = null;

        [Required, Range(typeof(bool), "false", "false", ErrorMessage = "77 + 33 = 100 is a false statement, unfortunately.")]
        public bool? IsSelectMathStatementTrue { get; set; } = null;

        [Required, Range(typeof(bool), "true", "true", ErrorMessage = "7 * 3 = 21 is a true statement.")]
        public bool IsRadioMathStatementTrue { get; set; } = false;

        [Required, StringLength(10), CustomValidationClassName(Valid = "valid-socks", Invalid = "invalid-socks")]
        public string SocksColor { get; set; }

        [Required, MinLength(2), MaxLength(3)]
        public City[] SelectedCities { get; set; } = new[] { City.SanFrancisco };

        [Required]
        public string[] HostileStrings { get; set; } = new string[] { "\"", "{" };

        public string Username { get; set; }
    }

    enum TicketClass { Economy, Premium, First }

    enum Airline { BestAirline, CoolAirline, NoNameAirline, Unknown }

    enum Color { Red, Green, Blue, Orange }

    enum Country { Japan, Yemen, Latvia }

    enum City { SanFrancisco, Tokyo, London, Madrid }

    List<string> submissionLog = new List<string>(); // So we can assert about the callbacks

    void HandleValidSubmit()
    {
        submissionLog.Add("OnValidSubmit");
    }

    void TriggerAsyncValidationError()
    {
        customValidationMessageStore.Clear();

        // Note that this method returns void, so the renderer doesn't react to
        // its async flow by default. This is to simulate some external system
        // implementing async validation.
        Task.Run(async () =>
        {
            // The duration of the delay doesn't matter to the test, as long as it's not
            // so long that we time out. Picking a value that's long enough for humans
            // to observe the asynchrony too.
            await Task.Delay(500);
            customValidationMessageStore.Add(editContext.Field(nameof(Person.Username)), "This is invalid, asynchronously");
            _ = InvokeAsync(editContext.NotifyValidationStateChanged);
        });
    }

    void ToggleDataAnnotations()
    {
        enableDataAnnotationsSupport = !enableDataAnnotationsSupport;
        submissionLog.Add($"DataAnnotations support is now {(enableDataAnnotationsSupport ? "enabled" : "disabled")}");
    }
}
